/*
 * Copyright (c) 2010 Toni Spets <toni.spets@iki.fi>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <windows.h>
#include <stdio.h>

/* remember kids, do not do this at home */
#include "inject.c"

#ifdef USE_BROKEN_UPDATER
BOOL Updater();
#endif

/*
   game search order: cnc95, ra, tibsun, ra2
   general search order:
    1) command line parameter
    2) search for any game exe in reverse order from current directory
    2) search for any game from other possible locations (reg, def install path)
*/

#define GAME_NO     0
#define GAME_CNC95  1100
#define GAME_RA     1101
#define GAME_SUN    1102
#define GAME_RA2    1103
#define GAME_RA2YR  1104

struct game_info
{
    char name[64];
    char exe[16];
    char cmd[16];
    char reg[MAX_PATH];
    char path[MAX_PATH];
    char path_tfd[MAX_PATH];
    char path_tfd64[MAX_PATH];
    int resId;
    char path_found[MAX_PATH];
};

struct game_info games[] =
{
    {
        "Command & Conquer",
        "C&C95.exe",
        "-CNC95",
        "Software\\Westwood\\Command & Conquer Windows 95 Edition",
        "C:\\Westwood\\C&C95",
        "C:\\Program Files\\EA Games\\Command & Conquer The First Decade\\Command & Conquer(tm)",
        "C:\\Program Files (x86)\\EA Games\\Command & Conquer The First Decade\\Command & Conquer(tm)",
        GAME_CNC95,
        ""
    },
    {
        "Command & Conquer: Red Alert",
        "RA95.exe",
        "-RA",
        "Software\\Westwood\\Red Alert Windows 95 Edition",
        "C:\\Westwood\\REDALERT",
        "C:\\Program Files\\EA Games\\Command & Conquer The First Decade\\Command & Conquer Red Alert(tm)",
        "C:\\Program Files (x86)\\EA Games\\Command & Conquer The First Decade\\Command & Conquer Red Alert(tm)",
        GAME_RA,
        ""
    },
    {
        "Command & Conquer: Tiberian Sun",
        "SUN.exe",
        "-SUN",
        "Software\\Westwood\\Tiberian Sun",
        "C:\\Westwood\\SUN",
        "C:\\Program Files\\EA Games\\Command & Conquer The First Decade\\Command & Conquer(tm) Tiberian Sun(tm)\\SUN",
        "C:\\Program Files (x86)\\EA Games\\Command & Conquer The First Decade\\Command & Conquer(tm) Tiberian Sun(tm)\\SUN",
        GAME_SUN,
        ""
    },
    {
        "Command & Conquer: Red Alert 2",
        "RA2.exe",
        "-RA2",
        "Software\\Westwood\\Red Alert 2",
        "C:\\Westwood\\RA2",
        "C:\\Program Files\\EA Games\\Command & Conquer The First Decade\\Command & Conquer Red Alert(tm) II\\RA2",
        "C:\\Program Files (x86)\\EA Games\\Command & Conquer The First Decade\\Command & Conquer Red Alert(tm) II\\RA2",
        GAME_RA2,
        ""
    },
    {
        "Command & Conquer: Red Alert 2 Yuri's Revenge",
        "RA2MD.exe",
        "-RA2YR",
        "Software\\Westwood\\Yuri's Revenge",
        "C:\\Westwood\\RA2",
        "C:\\Program Files\\EA Games\\Command & Conquer The First Decade\\Command & Conquer Red Alert(tm) II\\RA2",
        "C:\\Program Files (x86)\\EA Games\\Command & Conquer The First Decade\\Command & Conquer Red Alert(tm) II\\RA2",
        GAME_RA2YR,
        ""
    },
    {
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        GAME_NO,
        ""
    }
};

BOOL FileExists(char *path)
{
    OFSTRUCT ofStruct;
    HFILE fh = OpenFile(path, &ofStruct, OF_EXIST);
    if(fh != HFILE_ERROR)
    {
        CloseHandle((HANDLE)fh);
        return TRUE;
    }
    return FALSE;
}

void UpdateGames(HWND hwndDlg)
{
    HKEY hKey;
    DWORD dwWritten;
    char tmp[MAX_PATH];
    struct game_info *game = &games[0];
    while( game->resId != GAME_NO )
    {
        // check in default install path
        snprintf(tmp, MAX_PATH, "%s\\%s", game->path, game->exe);
        if(FileExists(tmp))
        {
            strncpy(game->path_found, tmp, MAX_PATH);
        }

        // check in tfd 64-bit install path
        snprintf(tmp, MAX_PATH, "%s\\%s", game->path_tfd64, game->exe);
        if(FileExists(tmp))
        {
            strncpy(game->path_found, tmp, MAX_PATH);
        }

        // check in tfd install path
        snprintf(tmp, MAX_PATH, "%s\\%s", game->path_tfd, game->exe);
        if(FileExists(tmp))
        {
            strncpy(game->path_found, tmp, MAX_PATH);
        }

        // check in registry path
        dwWritten = MAX_PATH;
        if(RegOpenKey(HKEY_LOCAL_MACHINE, game->reg, &hKey) == ERROR_SUCCESS)
        {
            if(RegQueryValueEx(hKey, "InstallPath", NULL, NULL, (unsigned char*)tmp, &dwWritten) == ERROR_SUCCESS)
            {
                if(FileExists(tmp))
                {
                    strncpy(game->path_found, tmp, MAX_PATH);
                }
            }
        }

        // check for the exe in current directory
        if(FileExists(game->exe))
        {
            strncpy(game->path_found, game->exe, MAX_PATH);
        }

        game++;
    }
}

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    struct game_info *game = &games[0];

    switch(uMsg)
    {
        case WM_INITDIALOG:
            while( game->resId != GAME_NO )
            {
                if(game->path_found[0])
                {
                    EnableWindow(GetDlgItem(hwndDlg, game->resId), TRUE);
                }
                game++;
            }
            return TRUE;
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case GAME_CNC95:
                    EndDialog(hwndDlg, GAME_CNC95);
                    return TRUE;
                case GAME_RA:
                    EndDialog(hwndDlg, GAME_RA);
                    return TRUE;
                case GAME_SUN:
                    EndDialog(hwndDlg, GAME_SUN);
                    return TRUE;
                case GAME_RA2:
                    EndDialog(hwndDlg, GAME_RA2);
                    return TRUE;
                case GAME_RA2YR:
                    EndDialog(hwndDlg, GAME_RA2YR);
                    return TRUE;
                case IDCANCEL:
                    EndDialog(hwndDlg, IDCANCEL);
                    return TRUE;
            }
            break;
    }

    return FALSE;
}

#ifdef USE_BROKEN_UPDATER
void RemoveOldExe()
{
    char lpOldModuleFileName[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, lpOldModuleFileName, MAX_PATH);
    lpOldModuleFileName[strlen(lpOldModuleFileName)-1] = '_';
    if(FileExists(lpOldModuleFileName))
    {
        DeleteFile(lpOldModuleFileName);
    }
}
#endif

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    PROCESS_INFORMATION pInfo;
    STARTUPINFO sInfo;
    DWORD dwWritten;
    HANDLE hFile;

    char lpPathName[MAX_PATH] = { 0 };
    char lpTempFileName[MAX_PATH] = { 0 };
    HRSRC hResInfo;
    HGLOBAL hResData;
    int gameId;
    struct game_info *game;
    char *gamePath = NULL;

    char lpModuleFileName[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, lpModuleFileName, MAX_PATH);

#if USE_BROKEN_UPDATER
    RemoveOldExe();

    // check for updates
    if(Updater())
    {
        memset(&sInfo, 0, sizeof(STARTUPINFO));
        CreateProcessA(lpModuleFileName, lpCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &sInfo, &pInfo);
        return 0;
    }
#endif

    UpdateGames(0);

    // check if cmdline matches a game
    if(lpCmdLine[0])
    {
        game = &games[0];
        while( game->resId != GAME_NO )
        {
            if(strcmp(lpCmdLine, game->cmd) == 0)
            {
                gamePath = game->path_found;
                break;
            }
            game++;
        }
    }

    // check if a game exe is in the current directory
    if(!gamePath)
    {
        game = &games[0];
        while( game->resId != GAME_NO )
        {
            // it was a lot easier this way...
            if(strcmp(game->path_found, game->exe) == 0)
            {
                gamePath = game->path_found;
                break;
            }
            game++;
        }
    }

    // show select dialog
    if(!gamePath)
    {
        gameId = DialogBox(NULL, MAKEINTRESOURCE(1000), NULL, DialogProc);

#ifdef USE_BROKEN_UPDATER
        RemoveOldExe();
#endif

        if(gameId == IDCANCEL)
        {
            return 0;
        }

        game = &games[0];
        while( game->resId != GAME_NO )
        {
            if(game->resId == gameId && game->path_found[0])
            {
                gamePath = game->path_found;
            }
            game++;
        }

        if(gamePath == NULL)
        {
            return 0;
        }
    }

    GetTempPath(MAX_PATH, lpPathName);
    GetTempFileName(lpPathName, NULL, 0, lpTempFileName);

    hFile = CreateFile(lpTempFileName, GENERIC_WRITE, 0, NULL, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile == NULL)
    {
        return 1;
    }

    hResInfo = FindResource(NULL, MAKEINTRESOURCE(256), MAKEINTRESOURCE(10));
    if(hResInfo == NULL)
    {
        return 1;
    }

    hResData = LoadResource(NULL, hResInfo);
    if(hResData == NULL)
    {
        return 1;
    }

    if(WriteFile(hFile, LockResource(hResData), SizeofResource(NULL, hResInfo), &dwWritten, NULL) == FALSE)
    {
        return 1;
    }

    CloseHandle(hFile);

    memset(&sInfo, 0, sizeof(STARTUPINFO));

    // if game < GAME_SUN
    // HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers\<exe full path> = WIN95
    // backup original value? restore after CreateProcess?

    if(CreateInjectedProcessA(gamePath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &sInfo, &pInfo, lpTempFileName))
    {
        WaitForSingleObject(pInfo.hThread, INFINITE);
    }

    DeleteFile(lpTempFileName);

    return 0;
}
